package com.tan.user.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tan.common.constants.BlendConstants;
import com.tan.common.domain.user.SysMenu;
import com.tan.common.domain.user.SysRole;
import com.tan.common.dto.user.MenuCreateDTO;
import com.tan.common.dto.user.MenuUpdateDTO;
import com.tan.common.dto.user.RoleCreateDto;
import com.tan.common.dto.user.RoleUpdateDto;
import com.tan.common.enums.MenuTypeEnum;
import com.tan.common.mapper.user.SysMenuMapper;
import com.tan.common.mapstruct.user.MenuConvert;
import com.tan.common.tools.ConvertTool;
import com.tan.common.mapstruct.user.RoleConvert;
import com.tan.common.query.user.MenuQuery;
import com.tan.common.tools.PageTool;
import com.tan.common.tools.TreeTools;
import com.tan.common.vo.Option;
import com.tan.common.vo.user.MenuVO;
import com.tan.common.vo.user.RoleVO;
import com.tan.common.vo.user.RouteVO;
import com.tan.core.auth.AuthCurrentUser;
import com.tan.core.auth.AuthUtil;
import com.tan.core.enums.RedisKeyEnum;
import com.tan.core.utils.ExceptionTool;
import com.tan.core.utils.IdGenerate;
import com.tan.core.utils.RedissonTool;
import com.tan.core.utils.ThreadPoolTool;
import com.tan.user.service.ISysMenuService;
import com.tan.user.service.ISysRoleMenuService;
import com.tan.user.service.ISysRoleService;
import com.tan.user.service.ISysUserRoleService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单管理 服务实现类
 * </p>
 *
 * @author Tan
 * @since 2025-01-27
 */
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {

    @Resource
    private ISysUserRoleService userRoleService;

    @Resource
    private ISysRoleMenuService roleMenuService;

    @Resource
    private ISysRoleService roleService;

    @Resource
    private RedissonTool redissonTool;

    @Resource
    private ThreadPoolTool threadPoolTool;

    @Override
    public IPage<MenuVO> menuPages(MenuQuery query) {
        LambdaQueryWrapper<SysMenu> wrapper = Wrappers.lambdaQuery(SysMenu.class);
        wrapper.eq(query.getType() != null, SysMenu::getType, query.getType());
        wrapper.eq(query.getParentId() != null, SysMenu::getType, query.getParentId());
        wrapper.like(com.tan.core.utils.StringUtils.isNotBlank(query.getName()), SysMenu::getName, query.getName());
        wrapper.orderByDesc(SysMenu::getSort);
        IPage<SysMenu> pages = this.baseMapper.selectPage(new Page<>(query.getCurrent(), query.getSize()), wrapper);
        return PageTool.convert(pages, MenuConvert.class);
    }

    @Override
    public void createMenu(MenuCreateDTO menuCreateDTO) {
        SysMenu menu = MenuConvert.INSTANCE.dtoDomain(menuCreateDTO);
        int count = this.baseMapper.insert(menu);
        ExceptionTool.operateSqlError(count, "创建菜单失败");
        this.clearAdminMenuCache();
    }

    @Override
    public void updateMenu(MenuUpdateDTO menuUpdateDTO) {
        SysMenu menu = MenuConvert.INSTANCE.dtoDomain(menuUpdateDTO);
        int count = this.baseMapper.updateById(menu);
        ExceptionTool.operateSqlError(count, "修改菜单失败");
        this.clearMenuCache(menu.getId());
    }

    @Override
    public void delMenu(Long menuId) {
        SysMenu menu = this.baseMapper.selectById(menuId);
        ExceptionTool.isNullError(menu, "此菜单不存在!");
        int count = this.baseMapper.deleteById(menuId);
        ExceptionTool.operateSqlError(count, "删除菜单失败");
        this.clearMenuCache(menuId);
    }

    @Override
    public List<Option> menuOptions() {
        List<SysMenu> menus = this.allMenus();
        List<Option> options =MenuConvert.INSTANCE.toOption(menus);
        return TreeTools.getTree(options,Option::getParentId,Option::getValue);
    }

    @Override
    public List<SysMenu> allMenus() {
        return this.baseMapper.selectList(Wrappers.lambdaQuery(SysMenu.class)
                .ne(SysMenu::getType, MenuTypeEnum.BUTTON.getValue()).orderByAsc(SysMenu::getSort));
    }

    @Override
    public List<SysMenu> allMenus(Set<Long> ids) {
        if (ids.isEmpty()) return Collections.emptyList();
        return this.baseMapper.selectList(Wrappers.lambdaQuery(SysMenu.class)
                .ne(SysMenu::getType, MenuTypeEnum.BUTTON.getValue())
                .in(SysMenu::getId, ids)
                .orderByAsc(SysMenu::getSort)
        );
    }

    @Override
    public List<SysMenu> loginUserMenus() {
        AuthCurrentUser authUser = AuthUtil.getCurrentUser();
        String key = RedisKeyEnum.USER_ROUTE.createKey(authUser.getUserId());
        List<SysMenu> sysMenus = redissonTool.getObject(key);
        if (sysMenus != null && !sysMenus.isEmpty()) return sysMenus;
        List<RoleVO> roles = roleService.userRoles(authUser.getUserId());
        List<Long> roleIds = roles.stream().map(RoleVO::getId).collect(Collectors.toList());
        List<String> codes = roles.stream().map(RoleVO::getCode).collect(Collectors.toList());
        if (codes.contains(BlendConstants.SUPER_ADMIN)) {
            sysMenus = this.allMenus();
        } else {
            Set<Long> menus = roleMenuService.rolesByMenu(roleIds);
            sysMenus = this.allMenus(menus);
        }
        redissonTool.cacheObjWeek(key, sysMenus);
        return sysMenus;
    }

    @Override
    public List<RouteVO> listRoutes() {
        List<SysMenu> menuList = this.loginUserMenus();
        return buildRoutes(BlendConstants.MENU_ROOT_NODE_ID, menuList);
    }

    /**
     * 递归生成菜单路由层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由层级列表
     */
    private List<RouteVO> buildRoutes(Long parentId, List<SysMenu> menuList) {
        List<RouteVO> routeList = new ArrayList<>();
        for (SysMenu menu : menuList) {
            if (menu.getParentId().equals(parentId)) {
                RouteVO routeVO = toRouteVo(menu);
                List<RouteVO> children = buildRoutes(menu.getId(), menuList);
                if (!children.isEmpty()) {
                    routeVO.setChildren(children);
                }
                routeList.add(routeVO);
            }
        }

        return routeList;
    }

    /**
     * 根据RouteBO创建RouteVO
     */
    private RouteVO toRouteVo(SysMenu routeBO) {
        RouteVO routeVO = new RouteVO();
        String routeName = StringUtils.capitalize(StrUtil.toCamelCase(routeBO.getPath(), '-'));  // 路由 name 需要驼峰，首字母大写
        routeVO.setName(routeName); // 根据name路由跳转 this.$router.push({name:xxx})
        routeVO.setPath(routeBO.getPath()); // 根据path路由跳转 this.$router.push({path:xxx})
        routeVO.setRedirect(routeBO.getRedirect());
        routeVO.setComponent(routeBO.getComponent());

        RouteVO.Meta meta = new RouteVO.Meta();
        meta.setTitle(routeBO.getName());
        meta.setIcon(routeBO.getIcon());
        List<String> list = new ArrayList<>();
        list.add("Admin");
        meta.setRoles(new ArrayList<>());
        meta.setHidden(false);
        // 【菜单】是否开启页面缓存
//        if (MenuTypeEnum.MENU.equals(routeBO.getType()) && ObjectUtil.equals(routeBO.getKeepAlive(), 1)) {
        meta.setKeepAlive(false);
//        }
        // 【目录】只有一个子路由是否始终显示
//        if (MenuTypeEnum.CATALOG.equals(routeBO.getType()) && ObjectUtil.equals(routeBO.getAlwaysShow(), 1)) {
        meta.setAlwaysShow(false);
//        }

        routeVO.setMeta(meta);
        return routeVO;
    }


    @Override
    public void clearMenuCache(Long menuId) {
        threadPoolTool.execute(() -> {
            List<Long> roles = this.roleMenuService.rolesByMenu(menuId);
            roleService.clearRoleCache(roles);
        });
    }

    private void clearAdminMenuCache() {
        redissonTool.delCache(RedisKeyEnum.USER_ROUTE.createKey("10001"));
    }


}
